#ifndef TYPE_HPP
#define TYPE_HPP

#include <stdlib.h>

enum AllTypes {
    TYPE_INT,
    TYPE_BOOL,
    TYPE_STRING,
    TYPE_CHAR,
    TYPE_DOUBLE,

    // seperator: types before this are primitives.
    TYPE_PRIMITIVE, 

    TYPE_ARROW,
    TYPE_LIST,
    TYPE_TUPLE,
    TYPE_RECORD,

    TYPE_POLY,
    TYPE_CONSTRUCTOR,

    TYPE_FUNC,
    TYPE_APPLY,
    TYPE_VAR,

    TYPE_ANY,
};

enum OperatorType {
    OPERATOR_ADD,
    OPERATOR_SUB,
    OPERATOR_MUL,
    OPERATOR_DIV,
    OPERATOR_SHIFT,
};

class TypeMeta {
public:
    void* operator new(size_t size);
};

class Type : public TypeMeta {
protected:
    AllTypes _type_code;

public:
    virtual ~Type() {}

    virtual bool support_operator(OperatorType ot) {
        return false;
    }

    bool is_primitive() {
        return _type_code < TYPE_PRIMITIVE;
    }

    bool is_arrow() {
        return _type_code == TYPE_ARROW;
    }

    bool is_type_con() {
        return _type_code == TYPE_CONSTRUCTOR;
    }

    bool is_type_func() {
        return _type_code == TYPE_FUNC;
    }

    bool is_type_var() {
        return _type_code == TYPE_VAR;
    }

    bool is_type_apply() {
        return _type_code == TYPE_APPLY;
    }

    virtual const char* to_string() {
        return "";
    }

    virtual bool equals(Type* t) {
        return false;
    }
};

class PrimitiveType : public Type {
public:
    virtual const char* to_string() {
        return "";
    }

    virtual bool equals(Type* t) {
        return this == t;
    }
};

class IntType : public PrimitiveType {
private:
    IntType() { _type_code = TYPE_INT; }

public:
    static IntType* get_instance() {
        static IntType instance;
        return &instance;
    }

    virtual const char* to_string() {
        return "Int";
    }

    virtual bool support_operator(OperatorType ot);
};

class BoolType : public PrimitiveType {
private:
    BoolType() { _type_code = TYPE_BOOL; }

public:
    static BoolType* get_instance() {
        static BoolType instance;
        return &instance;
    }

    virtual const char* to_string() {
        return "Bool";
    }
};

class DoubleType : public PrimitiveType {
private:
    DoubleType() { _type_code = TYPE_DOUBLE; }

public:
    static DoubleType* get_instance() {
        static DoubleType instance;
        return &instance;
    }

    virtual const char* to_string() {
        return "Double";
    }
};

class StringType : public PrimitiveType {
private:
    StringType() { _type_code = TYPE_STRING; }

public:
    static StringType* get_instance() {
        static StringType instance;
        return &instance;
    }

    virtual const char* to_string() {
        return "String";
    }

    virtual bool support_operator(OperatorType ot);
};

class CharType : public PrimitiveType {
private:
    CharType() { _type_code = TYPE_CHAR; }

public:
    static CharType* get_instance() {
        static CharType instance;
        return &instance;
    }

    virtual const char* to_string() {
        return "Char";
    }
    virtual bool support_operator(OperatorType ot);
};

// Type for *->*
class ArrowType : public Type {
private:
    static const int LEN = 128;
    Type* _src;
    Type* _dst;
    char* _name;

public:
    ArrowType(Type* src, Type* dst);
    ~ArrowType();

    Type* src() { return _src; }
    Type* dst() { return _dst; }

    void set_src(Type* t) { _src = t; }
    void set_dst(Type* t) { _dst = t; }

    virtual const char* to_string();

    virtual bool equals(Type* t);
};

class Token;

class UserDefType : public Type {
private:
    char*  _name;

public:
    UserDefType(Token* t);
    ~UserDefType();

    const char* name() { return _name; }
};

class AnyType : public Type {
private:
    AnyType() { _type_code = TYPE_ANY; }

public:
    static AnyType* get_instance() {
        static AnyType instance;
        return &instance;
    }

    virtual const char* to_string() {
        return "Any";
    }

    virtual bool equals(Type* t) {
        return this == t;
    }
};

class TypeVar : public Type {
private:
    char* _name;

public:
    TypeVar(Token* t);

    const char* name() { return _name; }
};

class TypeArgs : public TypeMeta {
private:
    Type** _args;
    int    _length;

public:
    int length()             { return _length; }
    void init_length(int len);

    Type** arguments()       { return _args; }
    void set_arg(int i, Type* tv);

    TypeArgs(Type** args, int length): _args(args), _length(length) {}
};

class TypeFunction : public Type {
private:
    TypeArgs* _args;
    Type*     _body;

public:
    TypeFunction(TypeArgs* args, Type* body): 
        _args(args), _body(body) { _type_code = TYPE_FUNC; }

    TypeArgs* args() { return _args; }
    Type* body()     { return _body; }

    Type* apply(TypeArgs* real_args);
};

class TypeFunctionApply : public Type {
private:
    Type*       _func;
    TypeArgs*   _real_args;

public:
    TypeFunctionApply(Type* func, TypeArgs* args):
        _func(func), _real_args(args) { _type_code = TYPE_APPLY; }

    Type* func() { return _func; }
    TypeArgs* rargs() { return _real_args; }
};

#endif

